package ru.amse.jsynchro.kernel.task;

import java.io.File;
import java.io.IOException;
import java.util.List;

import ru.amse.jsynchro.fileSystem.FileSystem;
import ru.amse.jsynchro.kernel.Difference;
import ru.amse.jsynchro.kernel.IDirectoryTree;
import ru.amse.jsynchro.kernel.Operation;
import ru.amse.jsynchro.kernel.util.FileSystemUtil;
import ru.amse.jsynchro.logging.ILogger;

/**
 * the parent abstract class for all types of tasks.
 * Contains implementations of working with differences
 * and factory method for creating it's descendants
 * 
 * @author miha
 */

public abstract class Task {
    protected IDirectoryTree first;
    protected IDirectoryTree second;
    protected boolean forceResolveConflicts;
    protected ILogger logger;
    
    protected Task(String firstFolder, FileSystem fs1, 
                String secondFolder, FileSystem fs2, 
                boolean forceResolveConflicts, ILogger logger) throws IOException {
        //TODO invoking method must check input args
//        if (!firstFolder.isDirectory() || !secondFolder.isDirectory()) {
//            throw new IllegalArgumentException("files must be folders");
//        }
        if (firstFolder != null) {
            first = fs1.getDirectoryTree(firstFolder);
            second = fs2.getDirectoryTree(secondFolder);
        }
        this.forceResolveConflicts = forceResolveConflicts;
        this.logger = logger;
    }
    
    public static Task createTask(String file1,
                               FileSystem fs1,
                               String file2, 
                               FileSystem fs2, 
                               boolean forceResolveConflicts,
                               TaskType taskType, 
                               ILogger logger) throws IOException {
        switch (taskType.getType()) {
        case TaskType.DIRECTED:
            return new DirectedTask(file1,fs1,file2, fs2, forceResolveConflicts, logger);
        case TaskType.DIRECTEDDEL:
            return new DirectedDelTask(file1,fs1,file2, fs2, forceResolveConflicts, logger);
        case TaskType.UNDIRECTED:
            return new UndirectedTask(file1,fs1,file2, fs2, forceResolveConflicts, logger);
        default: 
            return null;
        }
    }
    
    
    /**
     * analyzes task folders 
     * @return true, if changes needed
     */
    public abstract boolean analyze();
    
    /**
     * processes synchronization. Method {@code analyze} must be invoked before
     * 
     */
//    TODO analyze, synch protection
    public abstract void synchronize();
    
    
    
    /**
     * @param diffs
     * @param sourceTree
     * @param destinationTree
     */
    protected void applyChanges(List<Difference> diffs, 
            IDirectoryTree sourceTree, IDirectoryTree destinationTree) {
//        System.out.println(diffs == null);
        FileSystem destinationFS = destinationTree.getFS();
        FileSystem sourceFS = null;
        
        for (Difference c: diffs) {
            String relativePath = c.getPath();
            File sourceFile = new File(sourceTree.getPath(), relativePath);
            File destinationFile = new File(destinationTree.getPath(), relativePath);
            logger.info("begin to apply " + sourceTree.getPath() + '\\' + c);
            switch (c.getOperation()) {
            case ADD: 
                sourceFS = sourceTree.getFS();
                destinationFS = destinationTree.getFS();
                try {
                    if (sourceFS.isDirectory(sourceFile)) {
                        FileSystemUtil.copyDir(sourceFile, sourceFS, destinationFile, destinationFS);
                    } else {
                        System.out.println("copy");
                        FileSystemUtil.copyFile(sourceFile, sourceFS, destinationFile, destinationFS);
                    }
                } catch (IOException e) {
                    logger.exception(e);
                    
                }
                break;
            case REMOVE:
//                File deleted = new File(destination.getPath(), c.getPath());
                try {
                    if (!destinationFS.deleteFile(destinationFile)) {
                        logger.info("can't delete file " + destinationFile.getPath());
                    }
                } catch (IOException e) {
                    logger.exception(e);
                }
                break;
            case UPDATE:break;
          //TODO stubs
            
            case ATTR_READ:
//                File attributed = new File(destination.getPath(), c.getPath());
                try {
                    destinationFS.attrRead(destinationFile);
                } catch (IOException e) {
                    logger.exception(e);
                }
                break;
                
            case ATTR_WRITE:
                try {
                    destinationFS.attrWrite(destinationFile);
                } catch (IOException e) {
                    logger.exception(e);
                }
                break;
            case ATTR_EXECUTE:
                try {
                    destinationFS.attrExecute(destinationFile);
                } catch (IOException e) {
                    logger.exception(e);
                }
                break;    
            case ATTR_HIDDEN:
                try {
                    destinationFS.attrHidden(destinationFile);
                } catch (IOException e) {
                    logger.exception(e);
                }
                break; 
            case ATTR_READONLY:
                try {
                    destinationFS.attrReadOnly(destinationFile);
                } catch (IOException e) {
                    logger.exception(e);
                }
                break;
            }
            
            
        }
    }
    
    
    /**
     * prints required changes to console and resolves conflicts
     */
    public void showDifferences() {
        resolveConflicts();
        int diffsNumber = getDiffsNumber();
        if (diffsNumber == 0) {
            System.out.println("folders don't need any changes");
        } else { 
            System.out.println("requared changes:");
            List<String> strdiffs = getStringDifferences();
            for (String strdiff: strdiffs) {
                System.out.println(strdiff);
            }
        }
    }
    
//    protected void addAtomicChanges(IDirectoryTree tree, List<Difference> diffs, Difference diff) {
//        List<NodeData> subElemenets = tree.getSubElements(diff.getPath());
//        for (NodeData data: subElemenets) {
//            diffs.add(new Difference(data.getPath(), diff.getOperation(), data.isDirectory()));
//        }
//    }
    
    /**
     * search conflicts from differences and asks user to 
     * resolve them. If {@code forceREsolveConflicts} is enabled,
     * conflict file will be added to the first folder
     */
    protected abstract void resolveConflicts();
    
    /**
     * @return number of the all differences
     */
    public abstract int getDiffsNumber();
    
    /**
     * converts each diff to string presentation.
     * The path of tree node will be added in the path of diff.
     * @return a list of {@code String} diffs. 
     */
    protected abstract List<String> getStringDifferences();
    
    protected void makeStringDifferences(String root, List<Difference> diffs,List<String> result) {
        for (Difference d: diffs) {
            if (d.getOperation() != Operation.NOTHING) {
                result.add(root + File.separator + d.toString());
            }
        }
    }
}
